/*
 * Copyright (C) 2011, EADS France
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */
package xowl.interpreter.backends.rete;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.Map;
import xowl.interpreter.backends.rdf.RDFNode;

/**
 *
 * @author Laurent WOUTERS
 */
public abstract class GraceHashJoin<T1, T2> extends JoinStrategy implements Iterator<Couple> {
    private Map<RDFNode, Map<RDFNode, Map<RDFNode, Collection<T1>>>> mapT1s;
    private Map<RDFNode, Map<RDFNode, Map<RDFNode, Collection<T2>>>> mapT2s;
    private Iterator<Map.Entry<RDFNode, Map<RDFNode, Map<RDFNode, Collection<T1>>>>> iterOuter;
    private Iterator<Map.Entry<RDFNode, Map<RDFNode, Collection<T1>>>> iterMiddle;
    private Iterator<Map.Entry<RDFNode, Collection<T1>>> iterInner;
    private Iterator<T1> iterT1s;
    private Map.Entry<RDFNode, Map<RDFNode, Map<RDFNode, Collection<T1>>>> currentN1;
    private Map.Entry<RDFNode, Map<RDFNode, Collection<T1>>> currentN2;
    private Map.Entry<RDFNode, Collection<T1>> currentN3;
    private T1 currentT1;
    private Collection<T2> currentT2s;
    private Iterator<T2> iterT2s;
    private Couple nextCouple = null;
    
    public GraceHashJoin(BetaJoinNodeTest t1, BetaJoinNodeTest t2, BetaJoinNodeTest t3) {
        super(t1, t2, t3);
        mapT1s = new HashMap<RDFNode, Map<RDFNode, Map<RDFNode, Collection<T1>>>>();
        mapT2s = new HashMap<RDFNode, Map<RDFNode, Map<RDFNode, Collection<T2>>>>();
    }
    

    protected abstract <T1, T2> Couple createCouple(T1 t1, T2 t2);
    protected abstract <T1> RDFNode getValueForT1(T1 t1, BetaJoinNodeTest test);
    protected abstract <T2> RDFNode getValueForT2(T2 t2, BetaJoinNodeTest test);
    
    protected Iterator<Couple> joinGenerics(Collection<T1> tokens, Collection<T2> facts) {
        for (T1 token : tokens)
            insertT1(token);
        for (T2 fact : facts)
            insertT2(fact);
        iterOuter = mapT1s.entrySet().iterator();
        currentN1 = iterOuter.next();
        iterMiddle = currentN1.getValue().entrySet().iterator();
        currentN2 = iterMiddle.next();
        iterInner = currentN2.getValue().entrySet().iterator();
        currentN3 = iterInner.next();
        currentT2s = retrieveT2s();
        if (currentT2s == null)
            findNextKeyMatch();
        if (currentT2s != null) {
            if (iterT2s == null)
                iterT2s = currentT2s.iterator();
            iterT1s = currentN3.getValue().iterator();
            currentT1 = iterT1s.next();
            nextCouple = createCouple(currentT1, iterT2s.next());
        }
        return this;
    }
    
    @Override public boolean hasNext() { return (nextCouple != null); }
    @Override public Couple next() {
        Couple result = nextCouple;
        nextCouple = findNext();
        return result;
    }
    @Override public void remove() { }
    
    private Couple findNext() {
        while (iterT2s == null || !iterT2s.hasNext()) {
            while (iterT1s == null || !iterT1s.hasNext())
            {
                findNextKeyMatch();
                if (currentT2s == null)
                    return null;
                iterT1s = currentN3.getValue().iterator();
            }
            currentT1 = iterT1s.next();
            iterT2s = currentT2s.iterator();
        }
        return createCouple(currentT1, iterT2s.next());
    }
    
    private void findNextKeyMatch() {
        currentT2s = null;
        while (currentT2s == null) {
            if (!iterInner.hasNext()) {
                if (!iterMiddle.hasNext()) {
                    if (!iterOuter.hasNext()) {
                        return;
                    } else {
                        currentN1 = iterOuter.next();
                        iterMiddle = currentN1.getValue().entrySet().iterator();
                    }
                } else {
                    currentN2 = iterMiddle.next();
                    iterInner = currentN2.getValue().entrySet().iterator();
                }
            } else {
                currentN3 = iterInner.next();
                currentT2s = retrieveT2s();
            }
        }
        iterT2s = currentT2s.iterator();
    }
    
    private Collection<T2> retrieveT2s() {
        Map<RDFNode, Map<RDFNode, Collection<T2>>> sub1 = mapT2s.get(currentN1.getKey());
        if (sub1 == null)
            return null;
        Map<RDFNode, Collection<T2>> sub2 = sub1.get(currentN2.getKey());
        if (sub2 == null)
            return null;
        Collection<T2> sub3 = sub2.get(currentN3.getKey());
        return sub3;
    }
    
    private void insertT1(T1 t1) {
        RDFNode v1 = (test1 != null ? getValueForT1(t1, test1) : null);
        RDFNode v2 = (test2 != null ? getValueForT1(t1, test2) : null);
        RDFNode v3 = (test3 != null ? getValueForT1(t1, test3) : null);
        Collection<T1> collec = resolveT1(v1, v2, v3);
        collec.add(t1);
    }
    private void insertT2(T2 t2) {
        RDFNode v1 = (test1 != null ? getValueForT2(t2, test1) : null);
        RDFNode v2 = (test2 != null ? getValueForT2(t2, test2) : null);
        RDFNode v3 = (test3 != null ? getValueForT2(t2, test3) : null);
        Collection<T2> collec = resolveT2(v1, v2, v3);
        collec.add(t2);
    }
    private Collection<T1> resolveT1(RDFNode v1, RDFNode v2, RDFNode v3) {
        Map<RDFNode, Map<RDFNode, Collection<T1>>> sub1 = mapT1s.get(v1);
        if (sub1 == null) {
            sub1 = new HashMap<RDFNode, Map<RDFNode, Collection<T1>>>();
            Map<RDFNode, Collection<T1>> sub2 = new HashMap<RDFNode, Collection<T1>>();
            Collection<T1> sub3 = new ArrayList<T1>();
            mapT1s.put(v1, sub1);
            sub1.put(v2, sub2);
            sub2.put(v3, sub3);
            return sub3;
        }
        Map<RDFNode, Collection<T1>> sub2 = sub1.get(v2);
        if (sub2 == null) {
            sub2 = new HashMap<RDFNode, Collection<T1>>();
            Collection<T1> sub3 = new ArrayList<T1>();
            sub1.put(v2, sub2);
            sub2.put(v3, sub3);
            return sub3;
        }
        Collection<T1> sub3 = sub2.get(v3);
        if (sub3 == null) {
            sub3 = new ArrayList<T1>();
            sub2.put(v3, sub3);
        }
        return sub3;
    }
    private Collection<T2> resolveT2(RDFNode v1, RDFNode v2, RDFNode v3) {
        Map<RDFNode, Map<RDFNode, Collection<T2>>> sub1 = mapT2s.get(v1);
        if (sub1 == null) {
            sub1 = new HashMap<RDFNode, Map<RDFNode, Collection<T2>>>();
            Map<RDFNode, Collection<T2>> sub2 = new HashMap<RDFNode, Collection<T2>>();
            Collection<T2> sub3 = new ArrayList<T2>();
            mapT2s.put(v1, sub1);
            sub1.put(v2, sub2);
            sub2.put(v3, sub3);
            return sub3;
        }
        Map<RDFNode, Collection<T2>> sub2 = sub1.get(v2);
        if (sub2 == null) {
            sub2 = new HashMap<RDFNode, Collection<T2>>();
            Collection<T2> sub3 = new ArrayList<T2>();
            sub1.put(v2, sub2);
            sub2.put(v3, sub3);
            return sub3;
        }
        Collection<T2> sub3 = sub2.get(v3);
        if (sub3 == null) {
            sub3 = new ArrayList<T2>();
            sub2.put(v3, sub3);
        }
        return sub3;
    }
}
